package com.bolt.support.spring.jpa.jpql;

import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.reflect.ReflectionUtil;
import com.bolt.convention.data.DefaultPageInfo;
import com.bolt.convention.data.PageInfo;
import com.bolt.support.query.toolkit.JPAUtil;
import com.bolt.support.query.toolkit.ResultUtil;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import javax.persistence.*;
import java.lang.reflect.Field;
import java.util.*;

public abstract class AbstractJPAQuery<T, Q extends AbstractJPAQuery<T, Q>> extends BaseJPAQuery<T, Q> {

    protected final Multimap<String, Object> hints = LinkedHashMultimap.create();

    protected final EntityManager entityManager;

    protected LockModeType lockMode;

    protected FlushModeType flushMode;

    public AbstractJPAQuery(EntityManager em) {
        super(new DefaultQueryMetadata(em));
        this.entityManager = em;

    }

    private Query createQuery(boolean forCount) {
        JPQLBuilder builder = new JPQLBuilder();
        String queryString = builder.renderQuery(queryMetadata, forCount);
        Query query;
        if (queryMetadata.nativeQuery()) {
            query = entityManager.createNativeQuery(queryString);
        } else {
            query = entityManager.createQuery(queryString);
        }
        JPAUtil.setParameter(query, queryMetadata.getParam());
        if (lockMode != null) {
            query.setLockMode(lockMode);
        }
        if (flushMode != null) {
            query.setFlushMode(flushMode);
        }
        for (Map.Entry<String, Object> entry : hints.entries()) {
            query.setHint(entry.getKey(), entry.getValue());
        }
        Integer limit = queryMetadata.getLimit();
        Integer offset = queryMetadata.getOffset();
        if (limit != null) {
            query.setMaxResults(limit);
        }
        if (offset != null) {
            query.setFirstResult(offset);
        }
        return query;
    }

    private List<?> getResultList(Query query) {
        if (queryMetadata.getReturnClass() != null) {
            List<?> results = query.getResultList();
            List<Object> resultProcessors = queryMetadata.getResultProcessor();
            List<String> aliases = queryMetadata.getAliases();
            Class<?> returnClass = queryMetadata.getReturnClass();
            Map<String, Field> mappedFields = new HashMap<>();
            for (String alias : aliases) {
                mappedFields.put(alias, ReflectionUtil.getAccessibleField(returnClass, alias));
            }
            return ResultUtil.getResultList(results, aliases, resultProcessors, returnClass, mappedFields);
        } else {
            return query.getResultList();
        }
    }

    private Object getSingleResult(Query query) {
        Object result = null;
        try {
            result = query.getSingleResult();
        } catch (NoResultException ex) {
        }
        if (queryMetadata.getReturnClass() != null) {
            List<String> aliases = Clauses.toAliases(queryMetadata.getSelect());
            Class<?> returnClass = queryMetadata.getReturnClass();
            Map<String, Field> mappedFields = new HashMap<>();
            for (String alias : aliases) {
                mappedFields.put(alias, ReflectionUtil.getAccessibleField(returnClass, alias));
            }
            return ResultUtil.getSingleResult(result, aliases, returnClass, mappedFields);
        } else {
            return result;
        }
    }


    @Override
    public List<T> fetch() {
        Query query = createQuery(false);
        return (List<T>) getResultList(query);


    }

    @Override
    public T fetchOne() {
        Query query = createQuery(false);
        return (T) getSingleResult(query);
    }

    @Override
    public T fetchFirst() {
        return limit(1).fetchOne();
    }

    @Override
    public long fetchCount() {
        Query query = createQuery(true);
        return com.bolt.common.convert.Convert.toLong(query.getSingleResult());
    }

    @Override
    public PageInfo<T> fetchPage(Pageable pageable) {
        List<T> result = Collections.EMPTY_LIST;
        long total = fetchCount();
        if(queryMetadata.isSubCount()){
            queryMetadata.setNativeQuery(false);
        }
        if (total > 0) {
            Sort orderBy = pageable.getSort();
            List<OrderClause> orderClauses = new ArrayList<>();
            orderBy.forEach(o -> {
                Optional<ColumnClause> columnClause = findColumnInSelect(o.getProperty());
                if (columnClause.isPresent()) {
                    OrderClause orderClause = new OrderClause(o.getDirection().name(), columnClause.get());
                    orderClauses.add(orderClause);
                }
            });
            if (IteratorUtil.isNotEmpty(orderClauses)) {
                orderBy(orderClauses.toArray(new OrderClause[0]));
            }
            result = limit(pageable.getPageSize()).offset(pageable.getOffset()).fetch();

        }
        return new DefaultPageInfo(pageable.getPageNumber() + 1, pageable.getPageSize(), result, total);

    }

    public Q setHint(String name, Object value) {
        hints.put(name, value);
        return (Q) this;
    }
}

 